package certificate;

import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import sun.security.x509.*;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Date;
import org.apache.commons.codec.binary.Base64;

public abstract class X509CertificateHandler {
    
    /**
     * @param dName
     * 
     * X.500 Distinguished Name supports following subparts:
        -commonName - common name of a person, e.g., "Susan Jones"
    
        -organizationUnit - small organization (e.g, department or division) name, e.g., "Purchasing"

        -organizationName - large organization name, e.g., "ABCSystems, Inc."

        -localityName - locality (city) name, e.g., "Palo Alto"
                    
        -stateName - state or province name, e.g., "California"

        -country - two-letter country code, e.g., "CH"
        
        String dName has to be in the following format: CN=cName, OU=orgUnit, O=org, L=city, S=state, C=countryCode
                
                example: CN=Mark Smith, OU=JavaSoft, O=Sun, L=Cupertino, S=California, C=US
                
     * @param pair KeyPair with Public and Private keys
     * @param algorithm Name of the used algorithm, i.e. "SHA1withRSA"
     * @param daysValid Number of days the certificate will be valid since "now"
     * @return
     * @throws IOException
     * @throws CertificateException
     * @throws GeneralSecurityException
     */
    public static X509Certificate generate(String dName, KeyPair pair, String algorithm, int daysValid)
            throws IOException, CertificateException, GeneralSecurityException {
        PrivateKey privKey = pair.getPrivate();
        X509CertInfo info = new X509CertInfo();
        Date from = new Date();
        Date to = new Date(from.getTime() + daysValid * 86400000l);

        CertificateValidity interval = new CertificateValidity(from, to);
        BigInteger sn = new BigInteger(64, new SecureRandom());
        X500Name owner = new X500Name(dName);

        info.set(X509CertInfo.VALIDITY, interval);
        info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn));
        // self-signed
        info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(owner));
        info.set(X509CertInfo.ISSUER, new CertificateIssuerName(owner));
        info.set(X509CertInfo.KEY, new CertificateX509Key(pair.getPublic()));
        info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
        AlgorithmId algo = new AlgorithmId(AlgorithmId.sha1WithRSAEncryption_oid);
        info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algo));

        // Sign the cert to identify the algorithm that's used.
        X509CertImpl cert = new X509CertImpl(info);
        cert.sign(privKey, algorithm);

        // Update the algorith, and resign.
        algo = (AlgorithmId) cert.get(X509CertImpl.SIG_ALG);
        info.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, algo);
        cert = new X509CertImpl(info);
        cert.sign(privKey, algorithm);
        return cert;
    }
    
    /**
     * @param dName
     * 
     * X.500 Distinguished Name supports following subparts:
        -commonName - common name of a person, e.g., "Susan Jones"
    
        -organizationUnit - small organization (e.g, department or division) name, e.g., "Purchasing"

        -organizationName - large organization name, e.g., "ABCSystems, Inc."

        -localityName - locality (city) name, e.g., "Palo Alto"
                    
        -stateName - state or province name, e.g., "California"

        -country - two-letter country code, e.g., "CH"
        
        String dName has to be in the following format: CN=cName, OU=orgUnit, O=org, L=city, S=state, C=countryCode
                
                example: CN=Mark Smith, OU=JavaSoft, O=Sun, L=Cupertino, S=California, C=US
                
     * @param pair KeyPair with Public and Private keys
     * @param algorithm Name of the used algorithm, i.e. "SHA1withRSA"
     * @param daysValid Number of days the certificate will be valid since "now"
     * @param subject String Name of the subject of the certificate
     * @return
     * @throws IOException
     * @throws CertificateException
     * @throws GeneralSecurityException
     */
    public static X509Certificate generate(String dName, KeyPair pair, String algorithm, int daysValid, String subject)
            throws IOException, CertificateException, GeneralSecurityException {
        PrivateKey privKey = pair.getPrivate();
        X509CertInfo info = new X509CertInfo();
        Date from = new Date();
        Date to = new Date(from.getTime() + daysValid * 86400000l);

        CertificateValidity interval = new CertificateValidity(from, to);
        BigInteger sn = new BigInteger(64, new SecureRandom());
        X500Name owner = new X500Name(dName);
        X500Name sub = new X500Name(subject);

        info.set(X509CertInfo.VALIDITY, interval);
        info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn));
        info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(sub));
        info.set(X509CertInfo.ISSUER, new CertificateIssuerName(owner));
        info.set(X509CertInfo.KEY, new CertificateX509Key(pair.getPublic()));
        info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
        AlgorithmId algo = new AlgorithmId(AlgorithmId.sha1WithRSAEncryption_oid);
        info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algo));

        // Sign the cert to identify the algorithm that's used.
        X509CertImpl cert = new X509CertImpl(info);
        cert.sign(privKey, algorithm);

        // Update the algorith, and resign.
        algo = (AlgorithmId) cert.get(X509CertImpl.SIG_ALG);
        info.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, algo);
        cert = new X509CertImpl(info);
        cert.sign(privKey, algorithm);
        return cert;
    }


    public static void saveX509Certificate(X509Certificate X509Certificate, File file) throws CertificateEncodingException, IOException {
        String x509FileName = file.getName() + ".cer";
        String x509CertificateBase64 = null;
        Base64 encoder = new Base64();
        byte[] encodedBytes = encoder.encode(X509Certificate.getEncoded());
        x509CertificateBase64 = new String(encodedBytes);
        // add "-----BEGIN CERTIFICATE-----" and "-----END CERTIFICATE-----"
        String x509cer = "-----BEGIN CERTIFICATE-----\n" + x509CertificateBase64
                + "\n-----END CERTIFICATE-----\n";
        // save file
        BufferedWriter out = new BufferedWriter(new FileWriter(file));
        out.write(x509cer);
        out.close();
    }

    public static X509Certificate loadX509Certificate(File file) throws FileNotFoundException, IOException, CertificateException {
        FileInputStream fis = new FileInputStream(file);
        BufferedInputStream bis = new BufferedInputStream(fis);

        CertificateFactory cf = CertificateFactory.getInstance("X.509");

        X509Certificate cert = (X509Certificate) cf.generateCertificate(bis);
        return cert;
    }
}

